﻿
namespace NeuralNetworkPCL
{
    internal class NeuralNetwork
    {
        Neuron[] hiddenNeurons;
        Neuron[] outputNeurons;
        int nbInputs;
        int nbHidden;
        int nbOutputs;

        internal NeuralNetwork(int _nbInputs, int _nbHidden, int _nbOutput)
        {
            nbInputs = _nbInputs;
            nbHidden = _nbHidden;
            nbOutputs = _nbOutput;

            hiddenNeurons = new Neuron[nbHidden];
            for (int i = 0; i < nbHidden; i++)
            {
                hiddenNeurons[i] = new Neuron(_nbInputs);
            }

            outputNeurons = new Neuron[nbOutputs];
            for (int i = 0; i < nbOutputs; i++)
            {
                outputNeurons[i] = new Neuron(_nbHidden);
            }
        }

        internal double[] Evaluate(DataPoint _point)
        {
            foreach (Neuron n in hiddenNeurons)
            {
                n.Clear();
            }
            foreach (Neuron n in outputNeurons)
            {
                n.Clear();
            }

            double[] hiddenOutputs = new double[nbHidden];
            for (int i = 0; i < nbHidden; i++)
            {
                hiddenOutputs[i] = hiddenNeurons[i].Evaluate(_point);
            }
            double[] outputs = new double[nbOutputs];
            for (int outputNb = 0; outputNb < nbOutputs; outputNb++)
            {
                outputs[outputNb] =  outputNeurons[outputNb].Evaluate(hiddenOutputs);
            }

            return outputs;
        }

        internal void AdjustWeights(DataPoint _point, double _learningRate)
        {
            // Deltas for outputs
            double[] outputDeltas = new double[nbOutputs];
            for (int i = 0; i < nbOutputs; i++)
            {
                double output = outputNeurons[i].Output;
                double expectedOutput = _point.Outputs[i];
                outputDeltas[i] = output * (1 - output) * (expectedOutput - output);
            }

            // Deltas for hidden neurons 
            double[] hiddenDeltas = new double[nbHidden];
            for (int i = 0; i < nbHidden; i++)
            {
                double hiddenOutput = hiddenNeurons[i].Output;
                double sum = 0.0;
                for (int j = 0; j < nbOutputs; j++)
                {
                    sum += outputDeltas[j] * outputNeurons[j].Weight(i);
                }
                hiddenDeltas[i] = hiddenOutput * (1 - hiddenOutput) * sum;
            }

            double value;
            // Adjust output weights
            for (int i = 0; i < nbOutputs; i++)
            {
                Neuron outputNeuron = outputNeurons[i];
                for (int j = 0; j < nbHidden; j++)
                {
                    value = outputNeuron.Weight(j) + _learningRate * outputDeltas[i] * hiddenNeurons[j].Output;
                    outputNeuron.AdjustWeight(j, value);
                }
                // And bias
                value = outputNeuron.Weight(nbHidden) + _learningRate * outputDeltas[i] * 1.0;
                outputNeuron.AdjustWeight(nbHidden, value);
            }

            // Adjust hidden weights
            for (int i = 0; i < nbHidden; i++)
            {
                Neuron hiddenNeuron = hiddenNeurons[i];
                for (int j = 0; j < nbInputs; j++)
                {
                    value = hiddenNeuron.Weight(j) + _learningRate * hiddenDeltas[i] * _point.Inputs[j];
                    hiddenNeuron.AdjustWeight(j, value);
                }
                // And bias
                value = hiddenNeuron.Weight(nbInputs) + _learningRate * hiddenDeltas[i] * 1.0;
                hiddenNeuron.AdjustWeight(nbInputs, value);
            }
        }
    }
}
